package com.xxljob.demo.core.trigger;

import com.xxl.job.core.biz.ExecutorBiz;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.biz.model.TriggerParam;
import com.xxl.job.core.enums.ExecutorBlockStrategyEnum;
import com.xxl.job.core.util.ThrowableUtil;
import com.xxljob.demo.core.conf.XxlJobAdminConfig;
import com.xxljob.demo.core.model.XxlJobGroup;
import com.xxljob.demo.core.model.XxlJobInfo;
import com.xxljob.demo.core.model.XxlJobLog;
import com.xxljob.demo.core.route.ExecutorRouteStrategyEnum;
import com.xxljob.demo.core.scheduler.XxlJobScheduler;
import com.xxljob.demo.core.util.I18nUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
import java.util.Objects;

/**
 * @Author：YH
 * @Description：
 * @Date:Created in 2022/5/21 23:38
 */
public class XxlJobTrigger {

    private static final Logger LOGGER = LoggerFactory.getLogger(XxlJobTrigger.class);

    public static void trigger(int jobId,
                               TriggerTypeEnum triggerType,
                               int failRetryCount,
                               String executorShardingParam,
                               String executorParam,
                               String addressList) {

        // load data
        XxlJobInfo jobInfo = XxlJobAdminConfig.getAdminConfig().getXxlJobInfoDao().loadById(jobId);
        if (Objects.isNull(jobInfo)) {
            LOGGER.warn(">>>>>>>>>>>>>>trigger fail, jobId invalid，jobId={}", jobId);
            return;
        }
        if (Objects.nonNull(executorParam)) {
            jobInfo.setExecutorParam(executorParam);
        }
        int finalFailRetryCount = failRetryCount >= 0 ? failRetryCount : jobInfo.getExecutorFailRetryCount();
        XxlJobGroup group = XxlJobAdminConfig.getAdminConfig().getXxlJobGroupDao().load(jobInfo.getJobGroup());
        // cover addressList
        if (Objects.nonNull(addressList) && addressList.trim().length() > 0) {
            // 执行器地址类型：0=自动注册、1=手动录入
            group.setAddressType(1);
            group.setAddressList(addressList.trim());
        }

        //sharding param
        int[] shardingParam = null;
        if (Objects.nonNull(executorShardingParam)) {
            String[] shardingArr = executorShardingParam.split("/");
            if (shardingArr.length == 2 && isNumeric(shardingArr[0]) && isNumeric(shardingArr[1])) {
                shardingParam = new int[2];
                shardingParam[0] = Integer.valueOf(shardingArr[0]);
                shardingParam[1] = Integer.valueOf(shardingArr[1]);
            }
        }
        if (ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null) == ExecutorRouteStrategyEnum.SHARDING_BROADCAST
                && Objects.nonNull(group.getRegistryList())
                && !group.getRegistryList().isEmpty()
                && Objects.isNull(shardingParam)) {
            //注册列表  分片调度分别执行每一个执行器
            for (int i = 0; i < group.getRegistryList().size(); i++) {
                processTrigger(group, jobInfo, finalFailRetryCount, triggerType, i, group.getRegistryList().size());
            }
        } else {
            //非分片调度
            if (shardingParam == null) {
                shardingParam = new int[]{0, 1};
            }
            processTrigger(group, jobInfo, finalFailRetryCount, triggerType, shardingParam[0], shardingParam[1]);
        }
    }


    /**
     * @param group               job group, registry list may be empty
     * @param jobInfo
     * @param finalFailRetryCount
     * @param triggerType         触发类型
     * @param index               分片index
     * @param total               分片总数
     */
    private static void processTrigger(XxlJobGroup group,
                                       XxlJobInfo jobInfo,
                                       int finalFailRetryCount,
                                       TriggerTypeEnum triggerType,
                                       int index,
                                       int total) {


        // param
        //阻塞策略 默认单机串行
        ExecutorBlockStrategyEnum blockStrategy = ExecutorBlockStrategyEnum.match(jobInfo.getExecutorBlockStrategy(),
                ExecutorBlockStrategyEnum.SERIAL_EXECUTION);
        //路由策略
        ExecutorRouteStrategyEnum executorRouteStrategyEnum =
                ExecutorRouteStrategyEnum.match(jobInfo.getExecutorRouteStrategy(), null);
        //分片广播  0/1
        String shardingParam = (executorRouteStrategyEnum == ExecutorRouteStrategyEnum.SHARDING_BROADCAST) ?
                String.valueOf(index).concat("/").concat(String.valueOf(total)) : null;


        // 1、save log-id  保存日志
        XxlJobLog log = new XxlJobLog();
        //执行器主键ID
        log.setJobGroup(jobInfo.getJobGroup());
        log.setJobId(jobInfo.getId());
        log.setTriggerTime(new Date());
        long save = XxlJobAdminConfig.getAdminConfig().getXxlJobLogDao().save(log);
        LOGGER.debug(">>>>>>>>>>> xxl-job trigger start, jobId:{}", log.getId());

        // 2、init trigger-param
        TriggerParam triggerParam = new TriggerParam();
        triggerParam.setJobId(jobInfo.getId());
        triggerParam.setExecutorHandler(jobInfo.getExecutorHandler());
        triggerParam.setExecutorParams(jobInfo.getExecutorParam());
        triggerParam.setExecutorBlockStrategy(jobInfo.getExecutorBlockStrategy());
        triggerParam.setExecutorTimeout(jobInfo.getExecutorTimeout());
        triggerParam.setLogId(log.getId());
        triggerParam.setLogDateTime(log.getTriggerTime().getTime());
        triggerParam.setGlueType(jobInfo.getGlueType());
        triggerParam.setGlueSource(jobInfo.getGlueSource());
        triggerParam.setGlueUpdatetime(jobInfo.getGlueUpdatetime().getTime());
        triggerParam.setBroadcastIndex(index);
        triggerParam.setBroadcastTotal(total);


        // 3、init address
        String address = null;
        ReturnT<String> routeAddressResult = null;
        if (group.getRegistryList() != null && !group.getRegistryList().isEmpty()) {
            //分片广播的路由策略
            if (ExecutorRouteStrategyEnum.SHARDING_BROADCAST == executorRouteStrategyEnum) {
                if (index < group.getRegistryList().size()) {
                    address = group.getRegistryList().get(index);
                } else {
                    address = group.getRegistryList().get(0);
                }
            } else {
                routeAddressResult = executorRouteStrategyEnum.getRouter().route(triggerParam, group.getRegistryList());
                if (routeAddressResult.getCode() == ReturnT.SUCCESS_CODE) {
                    address = routeAddressResult.getContent();
                }
            }
        } else {
            routeAddressResult = new ReturnT<String>(ReturnT.FAIL_CODE, I18nUtil.getString(
                    "jobconf_trigger_address_empty"));
        }

        // 4、trigger remote executor 最终真实触发结果
        ReturnT<String> triggerResult = null;
        if (address != null) {
            triggerResult = runExecutor(triggerParam, address);
        } else {
            triggerResult = new ReturnT<String>(ReturnT.FAIL_CODE, null);
        }
    }


    /**
     * @param triggerParam:
     * @param address:http://localhost:9998
     * @Description:执行调度客户端
     * @return: com.xxl.job.core.biz.model.ReturnT<java.lang.String>
     **/
    public static ReturnT<String> runExecutor(TriggerParam triggerParam, String address) {
        ReturnT<String> runResult = null;
        try {
            ExecutorBiz executorBiz = XxlJobScheduler.getExecutorBiz(address);
            //调度客户端 真实的最终调度执行器
            runResult = executorBiz.run(triggerParam);
        } catch (Exception e) {
            LOGGER.error(">>>>>>>>>>> xxl-job trigger error, please check if the executor[{}] is running.", address, e);
            runResult = new ReturnT<String>(ReturnT.FAIL_CODE, ThrowableUtil.toString(e));
        }

        // runResult 触发调度：<br>address：http://localhost:9998<br>code：200<br>msg：null
        StringBuffer runResultSB = new StringBuffer(I18nUtil.getString("jobconf_trigger_run") + "：");
        runResultSB.append("<br>address：").append(address);
        runResultSB.append("<br>code：").append(runResult.getCode());
        runResultSB.append("<br>msg：").append(runResult.getMsg());
        runResult.setMsg(runResultSB.toString());
        return runResult;
    }

    private static boolean isNumeric(String str) {
        try {
            int result = Integer.valueOf(str);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }
}
